package org.msi.bayesiannetwork.ui;

import org.msi.bayesiannetwork.BayesianNetwork;
import org.msi.bayesiannetwork.BayesianNetworkConfigurationException;
import org.msi.bayesiannetwork.BayesianNetworkFactory;
import org.msi.bayesiannetwork.ExpressionEvaluationException;
import org.msi.bayesiannetwork.MarkovBlanket;
import org.msi.bayesiannetwork.RecursiveAlgorithm;

import javax.swing.*;
import javax.swing.filechooser.FileFilter;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.File;
import java.text.NumberFormat;


/**
 * <p>Represents the main frame.</p>
 *
 * @author Jakub Narloch
 * @version 1.0
 */
public class MainFrame extends JFrame {

    private BayesianNetwork bayesianNetwork;

    /**
     * <p>Represents the main panel.</p>
     */
    private JPanel mainPanel;

    private JButton browseButton;
    private JButton loadButton;
    private JButton evaluateButton;
    private JLabel expressionLabel;
    private JTextField expressionTextField;
    private JLabel fileLabel;
    private JTextField fileTextField;
    private JLabel inputLabel;
    private JLabel probabilityLabel;
    private JLabel resultLabel;
    private JTextField resultTextField;
    private JLabel titleLabel;
    private JLabel markovBlanketLabel;
    private JLabel markovBlanketInputLabel;
    private JComboBox markovBlanketInputNode;
    private JButton markovBlanketButton;
    private JLabel markovBlanketResultLabel;
    private JLabel markovBlanketResult;
    private JTextField markovBlanketFindResult;


    JFileChooser fileChooser;

    /**
     * <p>Creates new instance of {@link MainFrame} class.</p>
     */
    public MainFrame() {

        init();

        // set the initial state of the components
        expressionTextField.setEnabled(false);
        markovBlanketInputNode.setEnabled(false);
        evaluateButton.setEnabled(false);
        markovBlanketButton.setEnabled(false);

        browseButton.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {

                if (fileChooser.showOpenDialog(MainFrame.this) == JFileChooser.APPROVE_OPTION) {

                    File file = fileChooser.getSelectedFile();

                    fileTextField.setText(file.getAbsolutePath());
                }
            }
        });

        loadButton.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {

                expressionTextField.setEnabled(false);
                markovBlanketInputNode.setEnabled(false);
                markovBlanketInputNode.removeAllItems();
                evaluateButton.setEnabled(false);
                markovBlanketButton.setEnabled(false);
                expressionTextField.setText("");
                resultTextField.setText("");

                String filePath = fileTextField.getText();

                if (filePath.trim().isEmpty()) {
                    showErrorDialog("The file hasn't been specified.");
                    return;
                }

                try {
                    bayesianNetwork = BayesianNetworkFactory.loadFromFile(filePath);

                    expressionTextField.setEnabled(true);
                    evaluateButton.setEnabled(true);
                    markovBlanketButton.setEnabled(true);
                    markovBlanketInputNode.setEnabled(true);

                    for(String var : bayesianNetwork.getVariableList()) {
                        markovBlanketInputNode.addItem(var);
                    }

                } catch (BayesianNetworkConfigurationException exc) {

                    showErrorDialog(String.format("Error occurred when loading file: %s",
                            exc.getCause() != null ? exc.getCause().getMessage() : exc.getMessage()));
                }
            }
        });

        evaluateButton.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {

                resultTextField.setText("");

                double result;
                RecursiveAlgorithm recursiveAlgorithm = new RecursiveAlgorithm(bayesianNetwork);
                String expression = expressionTextField.getText();

                if (expression.trim().isEmpty()) {
                    showErrorDialog("The expression hasn't been specified.");
                    return;
                }

                try {
                    result = recursiveAlgorithm.evaluateExpression(expression);

                    NumberFormat format = NumberFormat.getNumberInstance();
                    format.setMaximumFractionDigits(8);

                    resultTextField.setText(format.format(result));
                } catch (ExpressionEvaluationException exc) {

                    showErrorDialog(String.format("Error occurred when evaluating expression: %s",
                            exc.getCause() != null ? exc.getCause().getMessage() : exc.getMessage()));
                }
            }
        });

        markovBlanketButton.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {

                markovBlanketFindResult.setText("");

                String expression = markovBlanketInputNode.getSelectedItem().toString();

                if (expression.trim().isEmpty()) {
                    showErrorDialog("Node hasn't been specified.");
                    return;
                }
                MarkovBlanket blanket = new MarkovBlanket(expression);
                markovBlanketFindResult.setText(blanket.findMarkovBlanket(bayesianNetwork));
            }
        });
    }

    /**
     * <p>Initializes this frame.</p>
     */
    private void init() {

        setTitle("Bayesian Network");
        setSize(430, 300);
        setDefaultCloseOperation(EXIT_ON_CLOSE);

        fileChooser = new JFileChooser();
        fileChooser.setCurrentDirectory(new File("."));
        fileChooser.setFileSelectionMode(JFileChooser.FILES_ONLY);
        fileChooser.setMultiSelectionEnabled(false);
        fileChooser.setFileFilter(new FileFilter() {
            @Override
            public boolean accept(File f) {
                return f.isDirectory() || f.getName().endsWith(".xml");
            }

            @Override
            public String getDescription() {
                return "Bayesian Network XML";
            }
        });

        mainPanel = new JPanel();
        mainPanel.setLayout(new BorderLayout());

        fileTextField = new JTextField(20);

        browseButton = new JButton("...");

        titleLabel = new JLabel("Bayesian Network:");

        fileLabel = new JLabel("File:");

        expressionLabel = new JLabel("Expression:");

        resultLabel = new JLabel("Result:");

        resultTextField = new JTextField(20);
        resultTextField.setEditable(false);

        probabilityLabel = new JLabel("Probability:");

        evaluateButton = new JButton("Evaluate");

        expressionTextField = new JTextField(20);

        inputLabel = new JLabel("Input:");

        loadButton = new JButton("Load");

        markovBlanketLabel = new JLabel("Markow blanket:");

        markovBlanketInputLabel = new JLabel("Node:");

        markovBlanketInputNode = new JComboBox();
        markovBlanketInputNode.setMinimumSize(new Dimension(180, 20));

        markovBlanketButton = new JButton("Find");

        markovBlanketResultLabel = new JLabel("Result:");

        markovBlanketResult = new JLabel("Node:");

        markovBlanketFindResult = new JTextField(20);
        markovBlanketFindResult.setEditable(false);

        javax.swing.GroupLayout layout = new javax.swing.GroupLayout(getContentPane());
        getContentPane().setLayout(layout);
        layout.setHorizontalGroup(
                layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                        .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup()
                                .addContainerGap(229, Short.MAX_VALUE)
                                .addComponent(mainPanel, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
                                .addGap(201, 201, 201))
                        .addGroup(layout.createSequentialGroup()
                                .addGap(67, 67, 67)
                                .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                                        .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup()
                                                .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING)
                                                        .addGroup(layout.createSequentialGroup()
                                                                .addComponent(expressionLabel)
                                                                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, 164, Short.MAX_VALUE)
                                                                .addComponent(loadButton))
                                                                ///
                                                        .addGroup(layout.createSequentialGroup()
                                                                .addComponent(markovBlanketLabel)
                                                                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, 164, Short.MAX_VALUE))

                                                        .addGroup(layout.createSequentialGroup()
                                                                .addComponent(markovBlanketInputLabel)
                                                                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, 164, Short.MAX_VALUE))

                                                        .addGroup(layout.createSequentialGroup()
                                                                .addComponent(markovBlanketResultLabel)
                                                                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, 164, Short.MAX_VALUE))

                                                        .addGroup(layout.createSequentialGroup()
                                                                .addComponent(markovBlanketResult)
                                                                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, 164, Short.MAX_VALUE))

                                                                ///


                                                        .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                                                                .addGroup(layout.createSequentialGroup()
                                                                        .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING)
                                                                                .addComponent(inputLabel)
                                                                                .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                                                                                        .addComponent(resultLabel)
                                                                                        .addComponent(probabilityLabel)))
                                                                        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                                                                        .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING, false)
                                                                                .addComponent(expressionTextField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
                                                                                .addComponent(markovBlanketInputNode, 0, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
                                                                                .addComponent(markovBlanketFindResult, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
                                                                                .addComponent(resultTextField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
                                                                        .addGap(51, 51, 51))
                                                                .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup()
                                                                        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, 200, javax.swing.GroupLayout.PREFERRED_SIZE)
                                                                        .addComponent(evaluateButton))
                                                                .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup()
                                                                        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, 200, javax.swing.GroupLayout.PREFERRED_SIZE)
                                                                        .addComponent(markovBlanketButton)))
                                                        .addGroup(layout.createSequentialGroup()
                                                                .addComponent(fileLabel)
                                                                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                                                                .addComponent(fileTextField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
                                                                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                                                                .addComponent(browseButton)))
                                                .addGap(88, 88, 88))
                                        .addGroup(layout.createSequentialGroup()
                                                .addComponent(titleLabel)
                                                .addContainerGap(273, Short.MAX_VALUE))))
        );
        layout.setVerticalGroup(
                layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                        .addGroup(layout.createSequentialGroup()
                                .addGap(32, 32, 32)
                                .addComponent(titleLabel)
                                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                                .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
                                        .addComponent(fileTextField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
                                        .addComponent(browseButton)
                                        .addComponent(fileLabel))
                                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
                                .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING)
                                        .addGroup(layout.createSequentialGroup()
                                                .addComponent(loadButton)
                                                .addGap(13, 13, 13)
                                                .addComponent(mainPanel, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
                                        .addComponent(expressionLabel))


                                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
                                .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
                                        .addComponent(expressionTextField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
                                        .addComponent(inputLabel))
                                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                                .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING)
                                        .addGroup(layout.createSequentialGroup()
                                                .addComponent(evaluateButton)
                                                .addGap(18, 18, 18)
                                                .addComponent(resultTextField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
                                        .addGroup(layout.createSequentialGroup()
                                                .addComponent(resultLabel)
                                                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                                                .addComponent(probabilityLabel)))
                                        ///place to start edit
                                .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING)
                                        .addGap(40, 40, 40)
                                        .addComponent(markovBlanketLabel))
                                .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                                        .addComponent(markovBlanketInputLabel)
                                        .addComponent(markovBlanketInputNode))
                                .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING)
                                        .addGap(30, 30, 30)
                                        .addComponent(markovBlanketButton))
                                .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING)
                                        .addComponent(markovBlanketResultLabel))

                                .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING)
                                        .addComponent(markovBlanketResult)
                                        .addComponent(markovBlanketFindResult))
                                        //
                                .addGap(82, 82, 82))
        );

        pack();
    }

    /**
     * <p>Shows this frame.</p>
     */
    public void showFrame() {

        setVisible(true);
    }

    /**
     * <p>Displays the error message.</p>
     *
     * @param msg the error message
     */
    private void showErrorDialog(String msg) {
        JOptionPane.showMessageDialog(this, msg, "Error", JOptionPane.ERROR_MESSAGE);
    }
}
